﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using net.windward.api.csharp;
using System.Data.Common;
using Kailua.net.windward.utils.ado.SPList.custom;
using net.windward.utils.ado;
using log4net;
using System.Security.Principal;
using Microsoft.SharePoint;

namespace ArrowWebParts
{
    /// <summary>
    /// A data source definition.  Contains information necessary to create a connection to a report data source.
    /// </summary>
    public class DataDefinition
    {
        #region Fields
        /// <summary>
        /// <para>Format string for exception message when an ADO.NET data source cannot be identified.</para>
        /// <para>"Could not identify the data source.\nTitle: {0}\nDescription: {1}\nFactory Class: {2}\nVendor GUID: {3}\nConnection String: {4}"</para>
        /// </summary>
        private const string CANT_ID_ADO_FMT = "Could not identify the data source.\nTitle: {0}\nDescription: {1}\nFactory Class: {2}\nVendor GUID: {3}\nConnection String: {4}";
        /// <summary>
        /// Logger.
        /// </summary>
        private static ILog log = LogManager.GetLogger(typeof(DataDefinition));
        #endregion Fields

        #region Properties
        /// <summary>
        /// Indicates if this is an XML data source.
        /// </summary>
        public bool IsXml
        {
            get
            {
                return FactoryClass != null && FactoryClass.ToLower() == "xml";
            }
        }
        /// <summary>
        /// The title of the data definition.
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// The description of the data definition.
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// <para>ADO.NET: the database connection string.</para>
        /// <para>XML: the URL.</para>
        /// </summary>
        public string ConnectionString { get; set; }
        /// <summary>
        /// For XML DataDefinitions, user name to use when accessing the data source.
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// For XML DataDefinitions, password to use when accessing the data source.
        /// </summary>
        public string Password { get; set; }
        /// <summary>
        /// The vendor name as defined in <see cref="WrVendor.name"/>.
        /// </summary>
        public string VendorName { get; set; }
        /// <summary>
        /// <para>For ADO.NET provider factories, provider invariant name as provided to 
        /// <see cref="DbProviderFactories.GetFactory(string providerInvariantName)"/> method.</para>
        /// <para>Use <see cref="IsXml"/> to to determine if this is an XML DataDefinition.</para>
        /// <para>Null for providers with no provider factory such as Windward's SharePoint list data source
        /// (<see cref="WrSPListVendor2"/>). For such providers, handle them based on their <see cref="VendorGUID"/>.</para>
        /// </summary>
        /// <seealso cref="AdoDataSourceInfo.FactoryClass"/>
        /// <seealso cref="WrVendor.ProviderClass"/>
        public string FactoryClass { get; set; }
        /// <summary>
        /// <para>This is the VendorGUID string defined in <see cref="WrVendor.GUID"/> for special
        /// data sources such as Windward's SharePoint list data source (<see cref="WrSPListVendor2"/>).</para>
        /// <para>This value is not used for ADO.NET providers with provider factories and is only necessary for identifying
        /// these special data sources.</para>
        /// </summary>
        public string VendorGUID { get; set; }
        #endregion Properties

        #region Constructors
        /// <summary>
        /// Creates a new DataDefinition using the specified values.
        /// </summary>
        /// <param name="_title">Title of the DataDefinition. Used as filename.</param>
        /// <param name="_description">Description of the DataDefintion.</param>
        /// <param name="_factoryClass">Provider invariant name. See <see cref="FactoryClass"/>.</param>
        /// <param name="_connStr">Connection string (URL for XML).</param>
        /// <param name="_vendorGuid">Unique ID of the vendor. See <see cref="VendorGUID"/>.</param>
        /// <param name="_vendorName">Vendor name.  See <see cref="VendorName"/>.</param>
        public DataDefinition(string _title, string _description, string _factoryClass, string _connStr, string _vendorGuid, string _vendorName)
        {
            Title = _title;
            Description = _description;
            FactoryClass = _factoryClass;
            ConnectionString = _connStr;
            VendorGUID = _vendorGuid;
            VendorName = _vendorName;
        }
        /// <summary>
        /// <para>Creates a DataDefinition by loading values from the specified .datadef XML file.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <param name="filePath">Full URL to the .datadef XML file.</param>
        public DataDefinition(string filePath)
        {
            constructFromXml(filePath);
        }
        /// <summary>
        /// Creates a DataDefinition by loading values from the specified .datadef XML file.
        /// </summary>
        /// <param name="fileStream">Stream containing the .datadef XML file.</param>
        public DataDefinition(Stream fileStream)
        {
            constructFromXmlStream(fileStream);
        }
        #endregion Constructors

        #region Methods
        /// <summary>
        /// <para>Creates an IReportDataSource for this DataDefinition.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <returns></returns>
        public IReportDataSource createIReportDataSource()
        {
            if (IsXml)
                return createXmlDataSourceImpl();
            else
                return createAdoDataSourceImpl();
        }
        /// <summary>
        /// <para>Creates an AdoDataSourceImpl for this DataDefinition.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <returns></returns>
        private AdoDataSourceImpl createAdoDataSourceImpl()
        {
            if (IsXml)
                throw new ArgumentException("Cannot construct DB data source from XML DataDefinition.");

            if ("61A1EDE2-5216-420e-BA87-5EEE3430128C".Equals(VendorGUID))
                return new AdoDataSourceImpl(typeof(SPListConnection), ConnectionString);

            // factory class can sometimes be null for WrSPListVendor2
            if (string.IsNullOrEmpty(FactoryClass))
                throw new WrDbException(string.Format(CANT_ID_ADO_FMT, Title, Description, FactoryClass, VendorGUID, ConnectionString));
            else
            {
                DbProviderFactory provider = DbProviderFactories.GetFactory(FactoryClass);
                Type typ = provider.CreateConnection().GetType();
                return new AdoDataSourceImpl(typ, ConnectionString);
            }
        }
        /// <summary>
        /// <para>Creates an XmlDataSourceImpl for this DataDefinition.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <returns></returns>
        private XmlDataSourceImpl createXmlDataSourceImpl()
        {
            // log user data
            if (log.IsDebugEnabled)
            {
                log.DebugFormat("dataDef.connStr={0}", ConnectionString);
                log.DebugFormat("user={0}", WindowsIdentity.GetCurrent().Name);
            }

            // try creating the xml data source using XmlUrlResolver via XmlDataSourceImpl
            try
            {
                // get the creds
                ICredentials creds = CredentialCache.DefaultNetworkCredentials;
                if (!string.IsNullOrEmpty(UserName))
                    creds = new NetworkCredential(UserName, Password);

                // create XmlDataSourceImpl
                return new XmlDataSourceImpl(ConnectionString, creds);
            }
            catch (Exception ex)
            {
                if (log.IsDebugEnabled)
                    log.Debug(string.Format("Failed to retrieve XML file {0} using url resolver.", ConnectionString), ex);
            }

            // try pulling the xml file via sharepoint OM
            bool originalCatchValue = SPSecurity.CatchAccessDeniedException;
            SPSecurity.CatchAccessDeniedException = false;
            XmlDataSourceImpl impl = null;
            try
            {
                Stream xmlFile = ArrowUtils.GetSPFileStream(ConnectionString);
                impl = new XmlDataSourceImpl(xmlFile, false);
            }
            catch (Exception ex)
            {
                if (log.IsDebugEnabled)
                    log.Debug(string.Format("Failed to retrieve XML file {0} using OM.", ConnectionString), ex);
            }
            finally
            {
                SPSecurity.CatchAccessDeniedException = originalCatchValue;
            }

            if (impl == null)
                throw new FileNotFoundException(string.Format("Failed to retrieve XML file {0}. Ensure the URL is correct and you have permission to access the file.", ConnectionString));

            return impl;
        }
        /// <summary>
        /// <para>Loads the DataDefinition from the specified .datadef XML file.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <param name="filePath">Full URL to the .datadef XML file.</param>
        private void constructFromXml(string filePath)
        {
            // read in the xml file
            Stream stream = ArrowUtils.GetSPFileStream(filePath);
            constructFromXmlStream(stream);
        }
        /// <summary>
        /// <para>Loads the DataDefinition from the specified .datadef XML file.</para>
        /// <para>Does not require SPContext.</para>
        /// </summary>
        /// <param name="stream">Stream containing the .datadef XML file.</param>
        private void constructFromXmlStream(Stream stream)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(stream);

            // these nodes must exist in the xml file
            Title = xmlDoc.SelectSingleNode("/datadef/title").InnerText;
            Description = xmlDoc.SelectSingleNode("/datadef/description").InnerText;
            FactoryClass = xmlDoc.SelectSingleNode("/datadef/className").InnerText;
            ConnectionString = xmlDoc.SelectSingleNode("/datadef/connectionString").InnerText;

            // these nodes may or may not exist in the xml file
            XmlNode ndUserName = xmlDoc.SelectSingleNode("/datadef/dataUserName");
            if (ndUserName != null)
                UserName = ndUserName.InnerText;
            XmlNode ndPassword = xmlDoc.SelectSingleNode("/datadef/dataPassword");
            if (ndPassword != null)
                Password = ndPassword.InnerText;
            XmlNode ndFactorySubtype = xmlDoc.SelectSingleNode("/datadef/factorySubtype");
            if (ndFactorySubtype != null)
                loadVendorGUID(ndFactorySubtype.InnerText);
            XmlNode ndVendorGuid = xmlDoc.SelectSingleNode("/datadef/vendorGuid");
            if (ndVendorGuid != null)
                VendorGUID = ndVendorGuid.InnerText;
            XmlNode ndVendorName = xmlDoc.SelectSingleNode("/datadef/vendor");
            if (ndVendorName != null)
                VendorName = ndVendorName.InnerText;
        }
        /// <summary>
        /// Loads the VendorGUID from the /datadef/factorySubtype. This is for backwards compatability
        /// with .datadef files that used FactorySubtype/ProviderSubtype before the field was replaced
        /// with a GUID. This is the GUID in the WrVendor.
        /// </summary>
        /// <param name="ndFactorySubtype"></param>
        private void loadVendorGUID(string factorySubtype)
        {
            if (factorySubtype.Equals("Access"))
                VendorGUID = "5E634265-A98A-42fa-96B7-E95761288C4F";
            if (factorySubtype.Equals("Excel"))
                VendorGUID = "DD6FD026-6F61-4987-9B82-65933FDB99F1";
            if (factorySubtype.Equals("SPList"))
                VendorGUID = "61A1EDE2-5216-420e-BA87-5EEE3430128C";
            if (factorySubtype.Equals("SharePointLists"))
                VendorGUID = "27C78A48-9922-49e3-BFCE-18757A9FB9CF";
        }
        /// <summary>
        /// Returns the DataDefinition serialized to an XmlDocument.
        /// </summary>
        /// <returns></returns>
        public XmlDocument toXml()
        {
            XmlDocument doc = new XmlDocument();
            XmlNode ndRoot = doc.CreateNode(XmlNodeType.Element, "datadef", null);
            doc.AppendChild(ndRoot);
            XmlNode ndTitle = doc.CreateNode(XmlNodeType.Element, "title", null);
            ndTitle.InnerText = Title;
            ndRoot.AppendChild(ndTitle);
            XmlNode ndDesc = doc.CreateNode(XmlNodeType.Element, "description", null);
            ndDesc.InnerText = Description;
            ndRoot.AppendChild(ndDesc);
            XmlNode ndConnStr = doc.CreateNode(XmlNodeType.Element, "connectionString", null);
            ndConnStr.InnerText = ConnectionString;
            ndRoot.AppendChild(ndConnStr);
            XmlNode ndFactoryClass = doc.CreateNode(XmlNodeType.Element, "className", null);
            ndRoot.AppendChild(ndFactoryClass);
            if (IsXml)
            {
                ndFactoryClass.InnerText = "xml";
                XmlNode ndUser = doc.CreateNode(XmlNodeType.Element, "dataUserName", null);
                ndUser.InnerText = UserName;
                ndRoot.AppendChild(ndUser);
                XmlNode ndPass = doc.CreateNode(XmlNodeType.Element, "dataPassword", null);
                ndPass.InnerText = Password;
                ndRoot.AppendChild(ndPass);
            }
            else
            {
                ndFactoryClass.InnerText = FactoryClass;
                XmlNode ndFactorySubtype = doc.CreateNode(XmlNodeType.Element, "vendorGuid", null);
                ndFactorySubtype.InnerText = VendorGUID;
                ndRoot.AppendChild(ndFactorySubtype);
                XmlNode ndVendorName = doc.CreateNode(XmlNodeType.Element, "vendor", null);
                ndVendorName.InnerText = VendorName;
                ndRoot.AppendChild(ndVendorName);
            }

            return doc;
        }
        #endregion Methods
    }
}
